/*
 * Copyright (C) 2011 Michael Widmer <widmm8@bfh.ch>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package ch.bfh.aeneas.navigation.route;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcelable;
import android.util.Log;
import ch.bfh.aeneas.R;
import ch.bfh.aeneas.exchange.intent.AeneasIntent;
import ch.bfh.aeneas.gui.RouteList;
import ch.bfh.aeneas.exchange.data.Node;
import ch.bfh.aeneas.exchange.data.Route;
import ch.bfh.aeneas.navigation.app.NavigationActivity;
import ch.bfh.aeneas.navigation.app.NavigationService.NavigationServiceBinder;
import ch.bfh.aeneas.navigation.RouteInfo;
import ch.bfh.aeneas.navigation.guide.GuideService;
import ch.bfh.aeneas.navigation.guide.GuideService.GuideServiceBinder;
import ch.bfh.aeneas.exchange.Scope;
import ch.bfh.aeneas.exchange.utils.DataConverter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


/**
 *
 * @author Michael Widmer <widmm8@bfh.ch>
 */
public class SmartRouteMe extends NavigationActivity {

    protected ServiceConnection mRouteConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName arg0, IBinder arg1) {
            mRouteService = (RouteService) ((NavigationServiceBinder) arg1).getService();
            mRouteService.addOnResultsListener(mResultsListener);
            mRouteStarted = true;
            checkRequirements();
        }

        public void onServiceDisconnected(ComponentName arg0) {
            stopListening();
        }        
    };
    
    protected ServiceConnection mGuideConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName arg0, IBinder arg1) {
            mGuideService = (GuideService) ((GuideServiceBinder) arg1).getService();
            mGuideStarted = true;
            checkRequirements();
        }

        public void onServiceDisconnected(ComponentName arg0) {
            mGuideStarted = false;
        }        
    };
    
    protected BroadcastReceiver mAnswerListener = new BroadcastReceiver() {
        @Override
        public void onReceive(Context arg0, Intent arg1) {
            try {
                Log.i(LOG_TAG, "Received answer... trying to handle");
                handleAnswer(arg1);
            } catch(Exception ex) {
                Log.w(LOG_TAG, "any unknown error happened: "+ex.getMessage());
            }
        }
    };
    
    protected Runnable trackChecker = new Runnable() {
        public void run() {
            checkTrack();            
        }        
    };
    
    protected enum RunMode {
        TO_POI,ROUTE,PICK;
    }
    
    private static final String LOG_TAG = "RouteMe";
    
    protected RouteService mRouteService;
    protected GuideService mGuideService;
    protected Boolean mRouteStarted;
    protected Boolean mGuideStarted;
    protected Route mMiddlePart;
    protected Route mStartPart;
    protected Route mEndPart;
    protected Route mTravelRoute;
    protected Node mCurrentLocation;
    protected Node mCurrentTarget;
    protected List<Route> mLocalRoutes;
    protected List<Route> mSubroutes;
    protected int mLocalId;
    protected int mRemoteId;
    protected int mStartId;
    protected int mTargetId;
    protected int mRequestId;
    protected int mStepNumber;
    protected Bundle mExtras;
    protected RunMode mMode;
    protected HashMap<Integer, Node[]> mRequestNodes;
    
    protected static final int PICK_CHOICE = 1;
    protected static final int PICK_DATA = 2;
    protected static final int PICK_SUBROUTE = 3;
    protected static final int PICK_STARTROUTE = 4;
    protected static final int PICK_ENDROUTE = 5;
    
    protected Handler mUpdater;
    
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle, R.layout.routeme);    
        
        //setContentView(R.layout.routeme);
        
        Intent start = getIntent();
        if(start.hasExtra("route")) {
            mMode = RunMode.ROUTE;
        } else if(start.hasExtra("to")) {
            mExtras = start.getExtras();
            if(start.hasExtra("from")) {
                mMode = RunMode.ROUTE;                
            } else {
                mMode = RunMode.TO_POI;
            }
        } else {
            mMode = RunMode.PICK;
        }
        
        mRouteStarted = false;
        mGuideStarted = false;
        mRequestNodes = new HashMap<Integer, Node[]>();
    }
    
    @Override
    public void onResume() {
        super.onResume();
        
        if(mMode == RunMode.PICK) {
            Intent i = new Intent(this, RoutePickMe.class);
            startActivityForResult(i,PICK_CHOICE);
        } else {        
            bindService(new Intent(this, RouteService.class),mRouteConnection,BIND_AUTO_CREATE);
            bindService(new Intent(this, GuideService.class),mGuideConnection,BIND_AUTO_CREATE);
            
            Intent out = AeneasIntent.getOutputIntent(getString(R.string.state_data_gets_loaded));
            super.sendBroadcast(out);            
        }
    }
    
    @Override
    public void onPause() {
        if(mRouteStarted) {
            mRouteService.removeOnResultsListener(mResultsListener);
            unbindService(mRouteConnection);
        }
        if(mGuideStarted) {
            unbindService(mGuideConnection);
        }
        super.onPause();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.i(LOG_TAG, "Received resultCode: "+resultCode+" and requestCode: "+requestCode);
        if(requestCode == PICK_SUBROUTE) {
            if(resultCode == Activity.RESULT_OK) {
                int selectedIndex = data.getIntExtra("selectedIndex", -1);
                Route r = mSubroutes.get(selectedIndex);
                
                finishRoute(r);
            }
            else if(resultCode == Activity.RESULT_CANCELED) {
            }
        } else if(requestCode == PICK_CHOICE) {
            if(resultCode == Activity.RESULT_OK) {
                mExtras = data.getExtras();
                if(data.hasExtra("route")) {
                    mMode = RunMode.ROUTE;
                } else if(data.hasExtra("to")) {
                    mMode = data.hasExtra("from")?RunMode.ROUTE:RunMode.TO_POI;
                }
            } else if(resultCode == Activity.RESULT_CANCELED) {
                finish();
            }
        }
    }

    @Override
    protected void onPositionConnected() {
        checkRequirements();
    }

    @Override
    protected void onPositionReceived(Location l) {
        mCurrentLocation = DataConverter.location2Node(l);
    }

    @Override
    protected void onResultReceived(int request, Intent intent) {
        if(request == mLocalId) {
            if(intent.hasExtra("route0")) {
                ArrayList<Route> routes = new ArrayList<Route>();
                
                Route r;
                int rCounter = 0;
                while(intent.hasExtra("route"+rCounter)) {
                    r = (Route) intent.getParcelableExtra("route"+rCounter);
                    routes.add(r);
                    rCounter++;
                }                        
                storeLocalRoutes(routes);
            }
        } else if(request == mStartId) {
            if(intent.hasExtra("route") && mStartPart != null) {
                mStartPart = (Route) intent.getParcelableExtra("route");
            }
            if(checkRouteParts()) {
                concatRoute();
                startTravelling();
            }
        } else if(request == mTargetId) {
            if(intent.hasExtra("route") && mEndPart != null) {
                mEndPart = (Route) intent.getParcelableExtra("route");
            }
            if(checkRouteParts()) {
                concatRoute();
                startTravelling();
            }
        } else if(request == mRemoteId) {
            
        } else if(request == mRequestId) {
            if(intent.hasExtra("routeCount")) {
                
            }
        }
    }

    @Override
    protected void onRequestFinished(int request) {
        if(request == mLocalId) {
            findNearestRoute();
        } else if(request == mRequestId) {
            
        }
    }

    @Override
    protected void onPositionStartListen() {
        if(mMode == RunMode.TO_POI) {
            if(mExtras.containsKey("to")) {
                Node n = (Node) mExtras.getParcelable("to");
                Node[] nodes = {mCurrentLocation, n};
                mLocalRoutes = new ArrayList<Route>();
                mLocalId = mRouteService.routeMeLocal(mCurrentLocation, n);                
                mRequestNodes.put(mLocalId, nodes);
            }
        } else if(mMode == RunMode.ROUTE) {
            if(mExtras.containsKey("route")) {
                
            } else if(mExtras.containsKey("to") && mExtras.containsKey("from")) {
                Node from = (Node) mExtras.getParcelable("from");
                Node to = (Node) mExtras.getParcelable("to");
                mStartId = mRouteService.routeMeRemote(mCurrentLocation, from);
                mTargetId = mRouteService.routeMeRemote(from, to);
            }
        }
    }

    protected void checkRequirements() {
        if(mRouteStarted && mGuideStarted && super.isPositionManagerReady()) {
            startListening();
        }
    }
    
    private void handleAnswer(Intent arg1) {
        int requestId = arg1.getIntExtra("requestId", -1);
        if(requestId == mRequestId) {
            
            if(arg1.hasExtra("route0")) {
                Route r;
                int rCounter = 0;
                ArrayList<Route> routes = new ArrayList<Route>();
                while(arg1.hasExtra("route"+rCounter)) {
                    r = (Route) arg1.getParcelableExtra("route"+rCounter);
                    routes.add(r);
                    rCounter++;
                }
                Log.i(LOG_TAG, "now containing "+routes.size()+" routes...");
                
                
            } else if(arg1.hasExtra("nodes")) {
                List<Node> nodes = (List<Node>) arg1.getSerializableExtra("nodes");
            }
        } else {
            Log.i(LOG_TAG, "Received answer with wrong requestId: "+requestId+", but expected: "+mRequestId);
        }
    }
    
    private void storeLocalRoutes(List<Route> routes) {
        mLocalRoutes.addAll(routes);
    }

    private void findNearestRoute() {
        
        Node[] ns = mRequestNodes.get(mLocalId);
        Node startNode = ns[0];
        Node targetNode = ns[1];
        
        Log.i(LOG_TAG, "Working with startNode: "+startNode+" and targetNode: "+targetNode);
        
        Route r;
        Route sr;
        RouteInfo ri;
        Node n;
        List<Node> nodes;
        float distance;
        
        mSubroutes = new ArrayList<Route>();
        ArrayList<RouteInfo> infos = new ArrayList<RouteInfo>();
        
        int nearestStartNode = -1;
        int nearestTargetNode = -1;
        float minDistanceStart;
        float minDistanceTarget;
        
        for(int i = 0; i < mLocalRoutes.size(); i++) {
            r = mLocalRoutes.get(i);
            Log.i(LOG_TAG, "Now treating route with name "+r.getName());
            ri = new RouteInfo(r.getName());
            nodes = r.getNodes();
            
            minDistanceStart = Integer.MAX_VALUE;
            minDistanceTarget = Integer.MAX_VALUE;
                
            for(int j = 0; j < nodes.size(); j++) {
                
                n = nodes.get(j);
                
                distance = n.distanceTo(startNode);
                if(distance < minDistanceStart) {
                    nearestStartNode = j;
                    minDistanceStart = distance;
                }
                
                distance = n.distanceTo(targetNode);
                if(distance < minDistanceTarget) {
                    nearestTargetNode = j;
                    minDistanceTarget = distance;
                }
            }
            
            //only add when nearest node to start is closer than nearest node to target
            if(minDistanceStart < minDistanceTarget) { 
                sr = r.getSubroute(nearestStartNode, nearestTargetNode);            
                mSubroutes.add(sr);

                ri.setLength(sr.getLength());
                ri.setDistanceToStart(minDistanceStart);
                ri.setDistanceToTarget(minDistanceTarget);
                infos.add(ri);
            }
        }
        
        Intent i = new Intent(this, RouteList.class);
        //Intent i = new Intent(AeneasIntent.ACTION_SELECT);
        //i.addCategory(AeneasIntent.CATEGORY_DATA);
        i.putExtra("values", infos);
        super.startActivityForResult(i, PICK_SUBROUTE);
    }
    
    protected void finishRoute(Route r) {
        mMiddlePart = r;
        Node startNode = r.getStart();
        Node targetNode = r.getEnd();
        mStartId = -1;
        mTargetId = -1;
        if(mCurrentLocation.distanceTo(startNode) > 100) {
            mStartId = mRouteService.routeMeLocal(mCurrentLocation, r.getStart());
        }
        if(targetNode.distanceTo(mCurrentTarget) > 100) {
            mTargetId = mRouteService.routeMeLocal(r.getEnd(), mCurrentTarget);
        }
        if(checkRouteParts()) {
            concatRoute();
            //startTravelling();
            
            mTravelRoute.first();
            //bindService(new Intent(this, GuideService.class),mGuideConnection,BIND_AUTO_CREATE);
        }
    }

    private boolean checkRouteParts() {
        boolean startOk = (mStartId > -1 && mStartPart == null) || mStartId == -1;
        boolean endOk = (mTargetId > -1 && mEndPart == null) || mTargetId == -1;
        
        return startOk && endOk;
    }
    
    private void concatRoute() {
        mTravelRoute = null;
        
        if(mStartId > -1) {
            mTravelRoute = mStartPart;
        }
        if(mTravelRoute != null) {
            mTravelRoute.addNodes(mMiddlePart.getNodes());
        } else {
            mTravelRoute = mMiddlePart;
        }
        if(mTargetId > -1) {
            mTravelRoute.addNodes(mEndPart.getNodes());
        }
    }
    
    private void startTravelling() {
        mUpdater = new Handler();
        mUpdater.postDelayed(trackChecker, 5000);
        //TODO: Settings
    }
    
    private void checkTrack() {
        Location to = DataConverter.node2Location(mTravelRoute.getCurrent());
        Location from = DataConverter.node2Location(mCurrentLocation);
        mGuideService.setTarget(to);
        int angle = mGuideService.getTimeAngle(from);
        int distance = Math.round(to.distanceTo(from));
        //TODO: set handling of angle relative to distance from target-point
        if(distance < 2) {
            if(mTravelRoute.next()) {
                to = DataConverter.node2Location(mTravelRoute.getCurrent());
                angle = mGuideService.getTimeAngle(from);
                distance = Math.round(to.distanceTo(from));
            } else {
                Intent finished = AeneasIntent.getOutputIntent("You've reached your target!");
                super.sendBroadcast(finished);
                finish();
            }
        }
        
        if(angle > 1 && angle < 11) {
            speech(super.getResources().getString(R.string.route_target_order)+distance+super.getResources().getString(R.string.guide_order_dist_unit)+angle+super.getResources().getString(R.string.guide_order_rot_unit));
        }
        mUpdater.postDelayed(trackChecker, 5000);
    }
}
